查看原文
其他

使用Unity Addressables可寻址资源系统

Rubén Bonet Unity官方平台 2022-05-07

Rubén Torres Bonet是一名优秀的游戏开发者,参与开发的游戏包括:《Time Stall》、《Catan Universe》、《Diamond Dash》、《Jelly Splash》等,这些游戏面向Oculus Quest、Android、iOS、PS4、Xbox One等多个平台,全球的累计安装量超过3000万次。

 

本文,Rubén Torres Bonet将分享使用Unity Addressables可寻址资源系统的方法。


引言

如果你正带领着一支由程序和美术组成的团队,在6个月内要完成将精美的PS4 VR游戏移植到Oculus Quest,并且面临只有1个月的时间处理内存减半的问题


你会怎么做?我们可以考虑使用Unity Addressables可寻址资源系统


在移植的过程中,我们必须同时处理多项复杂的任务,不同的开发者会有不同的考虑方向。但哪项任务会最耗费时间呢?


我猜超过70%的人会说:在把游戏移植到Oculus Quest平台时,CPU和GPU的性能是开发者面临最大的问题


性能是VR游戏中最难提升的方面之一对于这类优化,我们需要对产品有深入的了解。但这是一个极为耗时的过程,有时我们甚至无法进一步优化,只能牺牲大量游戏内容和图形效果。但这样导致移植出来的成品不符合玩家的期待,这也是非常棘手的问题。


“性能”这个词往往让开发者感到不寒而栗。Oculus Quest平台的性能是怎样的?项目在Oculus Quest的运行效果如何?


实际上,如果对Oculus Quest有一定开发经验,你可能知道,其实把Oculus Quest作为移动硬件来看,它的功能是非常强大的。Oculus Quest的主要区别在于它的主动冷却系统,该系统对可用CPU/GPU硬件级别提供了其它移动平台没有的大幅提升效果。


此外对于Oculus Quest的软件方面,和通用的Android变体相比,专用操作系统更加适合对虚拟现实渲染进行优化。


在过去几年中,移动硬件正在快速赶上独立平台。但实现持续在72fps下渲染依旧非常困难,特别是从高端平台移植的VR项目更确切地说,谈到Oculus Quest的移植时,我们必须把该平台设想为使用带有屏幕、电池、四个摄像头和一个风扇的高通骁龙835处理器的设备。


这款设备的缺点从其它角度看也可以是优点。该移动平台是经过大量研究的硬件,如今有很多已知的技巧可以快速把其CPU和GPU的负载降低到可以接受的程度。


我们任务重点是:和PS4相比,Oculus Quest减少了一半RAM内存。PS4的RAM内存是8GB,而Oculus Quest是4GB


这是粗略估算的结果,因为在两个平台上,操作系统都不会允许使用所有内存,这样它可以跟踪一些子系统,让生态系统正常工作。在Oculus Quest上,我们最多可以使用约2.2 GB的RAM内存,否则会出现问题

 

合适的内存处理对游戏来说非常重要,因为有两个限制:

  • 严格的内存限制:如果超过特定使用量阈值,操作系统会直接关闭游戏。

  • 缓冲的内存限制:除了特定限制外,在用户最小化游戏窗口,脱下VR设备,或走出Oculus Guardian区域时,操作系统也可能会关闭游戏,

 

我们不希望这些问题出现在游戏中,否则可以想想一名玩家在丢失2小时游戏进度后的愤怒。

 

 

2.2 GB的可用RAM内存其实并不多。对于从一开始就跟踪统计数据的新项目,这并不是问题,但如果要移植项目到性能大幅降低的硬件,这会是一个大问题。

 

如果过去处理过类似的移植过程,你一定明白减半游戏的RAM内存预算有多么困难。这项任务取决于游戏架构是否对这种改动做好准备。

 

减小内存压力的最常用方法包括:调整资源压缩设置、优化脚本、减少着色器变体等

 

根据项目的具体情况,调整纹理导入设置是我们的首选方法,但如果需要的话,我们也可以压缩网格、动画和音频。但这些方法本身非常复杂,而且有一定限制。

 

不是所有平台都支持相同的导入设置,面向多个设备构建会大幅提升构建管线的开销,更不用说QA阶段、美术、设计和编程方面的复杂性。开发者要考虑:某款Android设备是否支持ASTC或ETC2?

 

我们也希望构建64位的版本,同时在32位版本上留住玩家。我们要想好到底要创建多少个APK版本,更糟糕的是,游戏的每次更新都要对每个版本分别管理和测试。我们希望让工作变得轻松,所以我们不应该只依赖这些方法。

 

我们希望更进一步,让事情尽可能简单,特别是在进行移植的时候。为了性能而重新设计游戏是最坏的选择,不如不进行移植。

 

由于以上这些原因,们将介绍传统资源管理方法和可寻址资源管理系统。它将帮助你在数小时内减半项目的内存预算。


为了展示这个过程,我们会把简单的旧项目移植到全新的Unity Addressables可寻址资源系统

 

下图是使用Unity Addressables的最终效果图。

 

使用Unity Addressables的原因

我们的目标是分析如何轻松改进内存,快速实现改进效果。最有效最简单的实现方法是:加载初始场景,打开Profiler性能分析器


由于未优化的游戏架构可以在游戏中任意时间点频繁进行分析,所以最快捷的检查方法是:对初始场景进行性能分析。因为不少游戏经常有开销很大的脚本,它会造成资源引用过度消耗性能。


无论在特定时间是否使用某些资源,这类脚本组件会一直独立加载所有资源。如果游戏容易受到内存容量的限制,那么这是风险很大的使用方法,因为游戏无法随着开发者添加资源而扩展,例如:未来加入的DLC内容。


如果开发者要面向不同的设备开发,每种设备会提供不同的内存预算,我们必须考虑最糟糕的内存情况。


另一方面,是否有传统资源管理方法可以提供很好使用效果的情况呢?答案是肯定的。


如果你正在为PS4等同类平台开发项目,大多数要求已经在一开始决定好,使用全局对象的优点可能会胜过新内存管理系统的额外复杂度。


如果有足够好的使用效果,使用传统全局对象满足所有需求是一种很好的解决方案。这样可以简化代码,预加载所有引用的资源。在任何情况下,传统的内存管理方法不适合打算挑战硬件极限的开发者。


现在,我们开始使用Unity Addressables可寻址资源系统吧。


学习准备

我们需要使用Unity 2019.2.0f1或更高版本,然后从GitHub上下载的Level 1项目:

https://github.com/The-Gamedev-Guru/Unity-Addressables-Level-1/archive/master.zip


Unity Addressables - 红色天空盒


Level 1开发者:传统资源管理方法

我们从最简单的传统资源管理方法开始。在本示例中,需要在我们的组件中有天空盒材质的直接引用列表。

 

设置过程只需简单的三步:

  • 通过Git下载Level 1项目

  • 打开Unity Hub,添加Add按钮。找到下载目录,打开项目文件。

  • 在Windows系统,按下Ctrl+P,在Mac系统,按下CMD+P。


我们可以按下按钮修改天空盒。这种方法很原始,也非常乏味。


我们的项目结构围绕着两个主要系统。我们有SkyboxManager游戏对象,其中的组件是保存天空盒材质引用的主要脚本,可以根据UI事件来切换材质。

using UnityEngine;

 

public class SkyboxManager : MonoBehaviour

{

    [SerializeField] private Material[] _skyboxMaterials;

 

    public void SetSkybox(int skyboxIndex)

    {

        RenderSettings.skybox = _skyboxMaterials[skyboxIndex];

    }

}


SkyboxManager对象为UI系统提供功能,可以通过使用RenderSettings API,应用特定材质到场景设置。


其次,我们有CanvasSkyboxSelector。该游戏对象包含一个画布组件,用于渲染一组垂直分布的按钮。


点击每个按钮时,它会调用之前提到的Manager函数,根据按钮ID交换渲染的天空盒。也就是,每个按钮的OnClick事件会在Manager对象调用SetSkybox函数。


场景的层级窗口


我们按下Ctrl+7或Cmd+7,或者点击Window > Analysis > Profiler,打开性能分析器,点击顶部的“录制”按钮。


几秒后停止录制,查看各项指标信息:CPU,内存等。项目的性能结果很好。我们会专注于内存部分,简单的视图模式会展示以下内容。


简单的内存性能分析结果


考虑到我们只会在一段时间展示一个天空盒,纹理大小的数值非常夸张,这是我们会在很多未优化项目中发现的情况。


在示例场景中,我们只使用了几个天空盒而已。而在其它项目中往往会有角色、星球、音效、音乐等更多内容。


现在我们要把内存分析部分切换为细节模式,然后进行查看。


详细的内存分析结果


我们发现所有天空盒纹理都加载到内存中,但每次只会显示一个天空盒。但这个简单的架构使用了400MB左右的内存


这样的结果无法令人满意,特别这仅仅是游戏的一小部分。解决这个问题的方法是我们下一部分所谈到的使用Unity Addressables可寻址资源系统。


Level 2开发者:使用Unity Addressables

开发游戏时,我们往往会从Level 1部分的情况开始,现在我们将学习使用Unity Addressables可寻址资源系统来进行处理。

 

访问GitHub获取Level 2项目文件:

https://github.com/The-Gamedev-Guru/Unity-Addressables-Level-2/archive/master.zip

 

通过对性能分析器的观察,我们知道,虽然每次只会使用一个天空盒,但是项目中所有天空盒都会加载到内存中。因为我们可能会遇到不同资源变体数量的限制,所以这并不是有可扩展性的解决方案。

 

现在,我们来学习使用Unity Addressables可寻址资源系统摆脱传统资源管理方法的束缚。


首先,添加新工具Unity Addressables可寻址资源系统的API。我们要安装Addressables资源包,打开Window > Package Manager,窗口如下图所示。

 

Unity资源包管理器


安装好Addressables资源包后,我们要把材质标记为可寻址资源,在检视窗口勾选Addressable。


Level 2资源管理方法:使用Unity可寻址资源


这可以让Unity在可寻址资源数据库包含这些材质和纹理依赖。该数据库会在我们的构建中用来打包资源为多个部分,从而在游戏中任意时候轻松加载。


打开Window > Asset Management > Addressables,此时会打开我们的数据库。


Level 2资源管理方法:主窗口

我们查看之前的SkyboxManager脚本,注意到该脚本仍旧保存着资源的直接引用。我们不希望这样,所以必须告诉脚本如何使用间接引用,即使用AssetReference。


下面,我们要优化脚本组件。

using UnityEngine;

using UnityEngine.AddressableAssets;

using UnityEngine.ResourceManagement.AsyncOperations;

 

public class SkyboxManager : MonoBehaviour

{

    [SerializeField] private List<AssetReference> _skyboxMaterials;

 

    private AsyncOperationHandle  _currentSkyboxMaterialOperationHandle;

 

    public void SetSkybox(int skyboxIndex)

    {

        StartCoroutine(SetSkyboxInternal(skyboxIndex));

    }

 

    private IEnumerator SetSkyboxInternal(int skyboxIndex)

    {

        if (_currentSkyboxMaterialOperationHandle.IsValid())

        {

            Addressables.Release(_currentSkyboxMaterialOperationHandle);

        }

 

        var skyboxMaterialReference = _skyboxMaterials[skyboxIndex];

        _currentSkyboxMaterialOperationHandle = skyboxMaterialReference.LoadAssetAsync();

        yield return _currentSkyboxMaterialOperationHandle;

        RenderSettings.skybox = _currentSkyboxMaterialOperationHandle.Result;

    }

}


我们对以上代码进行一些解释。


  • 主要改动发生在第7行,我们在此利用AssetReference保存了一组间接引用。修改后,材质在被引用时不会自动加载。我们必须明显指定加载操作,它们才会进行加载。之后,请在编辑器中重新指定该字段。


  • 第13行:由于现在使用异步工作流程,所以我们倾向于使用协程。我们会开启一个新的协程,用于处理天空盒材质的变化。


  • 在第18-20行,检查是否有天空盒材质的句柄,如果有,我们会释放之前渲染的天空盒。每次使用Addressables API执行这类加载操作时,我们都会得到为之后操作保存的句柄。句柄是一种数据结构,包含有关特定可寻址资源管理的数据。


  • 在第23行,我们将特定可寻址引用解析到天空盒材质,然后调用它的LoadAssetAsync函数。我们在第25行使用yield关键字,因此我们会在进一步处理前等待这项操作。由于使用了泛型,所以不需要开销较大的调用。


  • 最后,在材质和依赖加载后,我们会在第26行修改场景的天空盒。材质会在Result字段提供,该字段属于我们用来加载材质的句柄。


Level 2资源管理方法:AssetReference列表


现在我们查看该方法的实际效果,执行的步骤如下:

  • 在Addressables窗口中,单击Build Player Content,构建内容。

  • 然后为所选择的平台构建版本。

  • 运行版本,将它和内存分析器连接起来。


Build Player Content选项


如下图所示,我们看到性能分析果非常出色

内存分析器


优秀的性能分析结果可以改善多个方面,这意味着玩家可以在低端设备上玩你的游戏,也会为游戏开发者带来更多收入。这就是Addressables可寻址资源系统的强大功能


Addressables系统会给团队带来少量成本。程序员必须支持异步工作流程,使用协程很容易实现该工作流程。设计师需要学习Addressables系统的功能,例如:可寻址分组,积累经验来作出聪明的决策。

 

使用Unity Addressables可寻址资源系统,我们实现了以下效果:

  • 适当的内存管理功能。

  • 更快的初始加载速度。

  • 更快的安装速度,减少应用的存储大小。

  • 更好的设备兼容性。

  • 异步架构。

  • 能够在网上存储内容,把数据和代码互相分离。

 

进阶内容:实例化和引用计数

我们将Unity Addressables工作流程应用到了天空盒,由于每次只会启用一个天空盒,所以过程非常简单,但我们在游戏中要管理的东西不只是天空盒。


例如,我们可能会给游戏角色添加变体。这个角色可能是玩家角色,也可能是敌人角色,这样难度就变大了。


现在我们不只是要卸载资源并加载新资源,我们可能打算卸载的资源正在被其它实例使用,这种情况下如何进行内存管理呢?


Unity可以使用自带的集成引用计数器来处理这种情况。这意味着:每次我们实例化某个资源时,它会自动增加引用数量。每次我们卸载资源时,引用计数会减小。如果引用数量减小为0,即不存在任何实例,那么该资源便可从内存卸载。


这些操作可以使用下面的结构完成。

var operationHandle = prefabMaterialReference.InstantiateAsync(transform, true);

yield return operationHandle;

// ...

Addressables.ReleaseInstance(operationHandle);


请注意:低于Unity 2019的版本中Instantiate和Destroy不会更新引用计数,这些计数只受到Addressables系统的InstantiateAsync和ReleaseInstance变体的影响。因此,我们要避免在使用Addressables系统时,使用到Instantiate和Destroy。


其它加载方式

如果你希望使用硬编码的资源字符串标识符,而不是使用AssetReferences,我们可以在Manager类中使用其它结构。


它们的行为是相似的,该结构会返回开发者可以命令的async操作句柄。由于它有泛型形式,Result字段会设为我们最初传入方法调用的类型。

_currentSkyboxMaterialOperationHandle = Addressables.LoadAssetAsync<Material>("Skybox" + skyboxIndex);


我们传入函数的字符串标识符不仅可以在资源检视窗口设置,也可以在Addressables主窗口设置。此外,我们也可以加载某个标签的所有资源,这样有助于预加载所有会在后面关卡生成的敌人。


在网络提供内容


Level 3 开发者: 进阶使用Addressables

在前面两部分内容中,我们通过从传统资源管理系统转为Addressables系统的工作流程,实现了很好的性能提升。只需要少量的时间成本,我们就可以更好扩展项目中的资源,同时保持较低的内存使用量。


我们目前只探索了Addressables系统的部分功能,我们仍可以使用这个不同寻常的系统通过多种方法改进项目。

 

对于Addressables系统,我们不必记住所有详细功能,但建议开发者对所有功能进行大致的了解,因为在开发过程中,我们可能会遇到更多挑战。


了解Addressables的全部功能会给我们提供帮助,因此我们准备了额外的指南,你可以在指南中了解以下内容:

  • Addressables窗口。

  • Addressables系统的分析:内存泄漏会产生很大问题。

  • 过网络供给减少用户等待的时间。

  • 构建管线集成。

  • 实际方法:加速工作流程,不必花10分钟以上的时间等待。


更重要的是,该指南可以回答下列问题:

  • Send Profiler Events有什么潜在影响?

  • AddressableAssetSettings API的实用性如何?

  • 如何把所有内容和BuildPlayerWindow API集成?

  • Fast Mode,Virtual Mode和Packed Mode的区别是什么?


请发送“可寻址指南”到微信公众号后台,获取指南文档下载地址。


小结

本文,我们介绍了项目挑战:在一个月内,为面向Oculus Quest开发的项目减少一半的内存预算。Unity Addressables可寻址资源系统帮助我们按时完成耗费时间的任务,避免对大量游戏内容进行重新设计,通过导入设置几乎无法实现这些目标。


Addressables可寻址资源系统是一个很有前景的系统,Unity开发人员正在努力改进该系统。它适用于正式制作,而且提供完善的文档。


我们可以把使用Addressables可寻址资源系统看做一项中期投入。投入一些时间,让项目在未来数月或来年得到提升。不仅我们从中受益,由于游戏可以支持更多设备,更多玩家也将能够玩你的游戏。

 

下载Unity Connect APP,请点击此处 观看更多Unity官方精彩视频,请关注“Unity官方”B站账户。


你可以访问Unity答疑专区留下你的问题,Unity社区和官方团队帮你解答:

Connect.unity.com/g/discussion


推荐阅读

使用Unity制作游戏AI

Unity Labs新一代AR/MR工具:Project MARS

使用Cinemachine设置3D格斗游戏的摄像机

《使命召唤手游》首周下载破亿,技术演讲免费观看

可寻址资源系统介绍

全新设备模拟器加速移动端迭代

Marza动画星球新作《The Peak》



喜欢本文,请点击“在看”

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存